////////////////////////////////////////////////////////////////////////////
//
//  CryEngine Source File.
//  Copyright (C), Crytek, 2010.
// -------------------------------------------------------------------------
//  File name:   CryTCPServiceFactory.cpp
//  Version:     v1.00
//  Created:     29/03/2010 by Paul Mikell.
//  Description: CCryTCPServiceFactory member definitions
// -------------------------------------------------------------------------
//  History:
//
////////////////////////////////////////////////////////////////////////////


#include "StdAfx.h"


#if defined( PS3 )
#include <netinet/tcp.h>
#endif


#include "CryTCPServiceFactory.h"


#include "CryTCPService.h"


#if defined( WIN32 ) || defined( XENON ) || defined( gringo )

typedef TIMEVAL			CRYTIMEVAL;

#elif defined( PS3 ) || defined ( LINUX ) || defined(CAFE)

typedef timeval			CRYTIMEVAL;

#else

#error Unsupported platform

#endif



// Description:
//	 Default constructor for CCryTCPServiceFactory.
/* ctor */
CCryTCPServiceFactory::CCryTCPServiceFactory()
:	m_services(),
	m_maxSockets( 0 ),
	m_numSockets( 0 ),
	m_pSockets( 0 ),
	m_cnt( 0 )
{
}


// Description:
//	 Destructor for CCryTCPServiceFactory.
/* dtor */
CCryTCPServiceFactory::~CCryTCPServiceFactory()
{
	Terminate( true );
	SAFE_DELETE_ARRAY( m_pSockets );
}


// Description:
//	 Initialise the ICryTCPServiceFactory.
// Arguments:
//	 maxSockets - maximum number of sockets
// Return:
//	 Result code.
ECryLobbyError
CCryTCPServiceFactory::Initialise(
	uint32					maxSockets )
{
	m_maxSockets = maxSockets;
	m_numSockets = 0;
	m_pSockets = new SOCKET[ m_maxSockets ];
	assert( m_pSockets );

	for ( uint32 i = 0; i < m_maxSockets; ++i )
	{
		m_pSockets[ i ] = INVALID_SOCKET;
	}

	return eCLE_Success;
}


// Description:
//	 Initialise the ICryTCPServiceFactory.
// Return:
//	 Result code.
ECryLobbyError
CCryTCPServiceFactory::Terminate(
	bool					isDestructing )
{
	m_servicesMutex.Lock();
	{
		for ( size_t i = 0; i < m_services.size(); ++i )
		{
			m_services[ i ]->Terminate( false );
		}

		m_services.clear();
	}
	m_servicesMutex.Unlock();

	return eCLE_Success;
}

ICryTCPServicePtr CCryTCPServiceFactory::GetTCPService(const char* pUrl, uint16 port)
{
	ICryTCPServicePtr		pResult = 0;

	m_servicesMutex.Lock();
	{
		for ( size_t i = 0; i < m_services.size(); ++i )
		{
			if ( m_services[ i ]->IsService( pUrl, port ) )
			{
				pResult = m_services[ i ];
				break;
			}
		}

		if ( !pResult )
		{
			pResult = CreateTCPService( pUrl, port );
			m_services.push_back( pResult );
		}
	}
	m_servicesMutex.Unlock();

	return pResult;
}


// Description:
//	 Tick.
// Arguments:
//	 tv - time.
void
CCryTCPServiceFactory::Tick(
	CTimeValue				tv )
{
	size_t						i = 0;
	ICryTCPServicePtr		pCurr = 0;
	CTimeValue				delta;

	if (m_lastTickTimer.GetValue()!=0)
	{
		delta=tv-m_lastTickTimer;
	}

	for ( ;; )
	{
		m_servicesMutex.Lock();
		{
			if ( i < m_services.size() )
			{
				pCurr = m_services[ i ];
			}
			else
			{
				pCurr = 0;
			}
		}
		m_servicesMutex.Unlock();

		if ( !pCurr )
		{
			break;
		}

		pCurr->Tick( delta );

		++i;
	}

	m_lastTickTimer=tv;
}


// Description:
//	 Increment reference count.
// Return:
//	 Reference count
int
CCryTCPServiceFactory::AddRef()
{
	return CryInterlockedIncrement( &m_cnt );
}


// Description:
//	 Decrement reference count.
// Return:
//	 Reference count
int
CCryTCPServiceFactory::Release()
{
	const int				nCount = CryInterlockedDecrement( &m_cnt );

	assert( nCount >= 0 );

	if ( nCount == 0 )
	{
		delete this;
	}
	else
	{
		if ( nCount < 0 )
		{
			assert( 0 );
			CryFatalError( "Deleting Reference Counted Object Twice" );
		}
	}

	return nCount;
}


// Description:
//	 Get number of free sockets.
// Return:
//	 Number of free sockets.
uint32
CCryTCPServiceFactory::NumFreeSockets()
{
	return m_maxSockets - m_numSockets;
}


// Description:
//	 Connect a socket.
// Arguments:
//	 addr - address.
//	 sockIdx - index of socket.
// Return:
//	 Result code.
ESocketError
CCryTCPServiceFactory::Connect(
	sockaddr_in&			addr,
	int32&					sockIdx )
{
	if ( !NumFreeSockets() )
	{
		return eSE_MiscFatalError;
	}

	for ( sockIdx = 0; sockIdx < (int32)m_maxSockets; ++sockIdx )
	{
		if ( m_pSockets[ sockIdx ] == INVALID_SOCKET )
		{
			break;
		}
	}

	m_pSockets[ sockIdx] = ::socket(AF_INET, SOCK_STREAM, NULL);
	if ( m_pSockets[ sockIdx ] < 0 || m_pSockets[ sockIdx ] == INVALID_SOCKET )
	{
		return eSE_MiscFatalError;
	}

	int						yes = 1;

	if ( ::setsockopt( m_pSockets[ sockIdx ], SOL_SOCKET, SO_REUSEADDR, ( const char * )&yes, sizeof ( int ) ) < 0 )
	{
		::closesocket( m_pSockets[ sockIdx ] );
		m_pSockets[ sockIdx] = INVALID_SOCKET;
		return eSE_MiscFatalError;
	}

#if defined( WIN32 ) || defined( XENON )
	if ( ::ioctlsocket( m_pSockets[ sockIdx ], FIONBIO, ( u_long* )&yes ) < 0 )
#elif defined( PS3 )
	if ( ::setsockopt( m_pSockets[ sockIdx ], SOL_SOCKET, SO_NBIO, ( const char* )&yes, sizeof ( int ) ) < 0 )
#endif 
	{
		::closesocket( m_pSockets[ sockIdx ] );
		m_pSockets[ sockIdx] = INVALID_SOCKET;
		return eSE_MiscFatalError;
	}

	// TCP_NODELAY required for ps3 and Win32 because of high latency connection otherwise

#if defined( PS3 ) || defined( WIN32 )

	if ( ::setsockopt( m_pSockets[ sockIdx ], IPPROTO_TCP, TCP_NODELAY, ( const char * )&yes, sizeof ( int ) ) < 0 )
	{
		::closesocket( m_pSockets[ sockIdx ] );
		m_pSockets[ sockIdx ] = INVALID_SOCKET;
		return eSE_MiscFatalError;
	}

#endif // defined( PS3 ) || defined( WIN32 )

	// Ignore return value of connect for non-blocking sockets.
	::connect( m_pSockets[ sockIdx ], ( sockaddr * )&addr, sizeof ( addr ) );

	++m_numSockets;
	return eSE_Ok;
}


// Description:
//	 Send data to a socket.
// Arguments:
//	 sockIdx - socket index.
//	 pData - data,
//	 dataLen - size of data
// Return:
//	 Result code.
ESocketError
CCryTCPServiceFactory::Send(
	int32					sockIdx,
	const char*				pData,
	size_t					dataLen,
	size_t&					sent )
{
	ESocketError			result;

	CRYTIMEVAL				timeout;
	fd_set					writable;
	fd_set					error;
	int						selectResult;
	int						sendResult;

	assert( ( sockIdx < (int32)m_maxSockets ) && ( sockIdx > -1 ) );

	sent = 0;

	if ( m_pSockets[ sockIdx ] != INVALID_SOCKET )
	{
		timeout.tv_sec = 0;
		timeout.tv_usec = 0;

		FD_ZERO( &writable );
		FD_SET( m_pSockets[ sockIdx ], &writable );
		FD_ZERO( &error );
		FD_SET( m_pSockets[ sockIdx ], &error );
		selectResult = ::select( (int)m_pSockets[sockIdx]+1, 0, &writable, &error, &timeout ); // Ian: SOCKET->int is bad, may lose upper 32 bits

		if ( FD_ISSET( m_pSockets[ sockIdx ], &writable ) )
		{
			sendResult = ::send( m_pSockets[ sockIdx ], pData, dataLen, 0 );

			if ( sendResult < 0 )
			{
				result = eSE_MiscFatalError;
			}
			else
			{
				sent = sendResult;
				result = eSE_Ok;
			}
		}
		else if ( FD_ISSET( m_pSockets[ sockIdx ], &error ) )
		{
			result = eSE_MiscFatalError;
		}
		else
		{
			result = eSE_Ok;
		}
	}
	else
	{
		result = eSE_MiscFatalError;
	}

	return result;
}


// Description:
//	 Receive data from a socket.
// Arguments:
//	 sockIdx - socket index.
//	 pData - data,
//	 dataLen - size of data
//	 endOfStream - has the end of the stream been reached,
// Return:
//	 Result code.
ESocketError
CCryTCPServiceFactory::Recv(
	int32					sockIdx,
	char*					pData,
	size_t&					dataLen,
	bool&					endOfStream )
{
	ESocketError			result = eSE_Ok;

	CRYTIMEVAL				timeout;
	fd_set					sockets;

	assert( ( sockIdx < (int32)m_maxSockets ) && ( sockIdx > -1 ) );

	timeout.tv_sec = 0;
	timeout.tv_usec = 0;

	if ( m_pSockets[ sockIdx ] != INVALID_SOCKET )
	{
		// Read reply data.

		FD_ZERO( &sockets );
		FD_SET( m_pSockets[ sockIdx ], &sockets );
		int sr = ::select( (int)m_pSockets[sockIdx]+1, &sockets, 0, 0, &timeout ); // SOCKET->int is bad, may lose upper 32 bits

		if ( FD_ISSET( m_pSockets[ sockIdx ], &sockets ) )
		{
			int						r = ::recv( m_pSockets[ sockIdx ], pData, dataLen, 0 );

			switch ( r )
			{
			case -1:

				result = eSE_MiscFatalError;
				dataLen = 0;
				endOfStream = true;
				break;

			case 0:

				result = eSE_Ok;
				dataLen = 0;
				endOfStream = true;
				break;

			default:
				if( r < 0 )
				{ // handle the difference in the ps3 API for recv, a negative number means an error
					result = eSE_MiscFatalError;
					dataLen = 0;
					endOfStream = true;
				}
				else
				{				
					result = eSE_Ok;
					dataLen = r;
					endOfStream = false;
				}
				break;
			}
		}
		else
		{
			result = eSE_Ok;
			dataLen = 0;
			endOfStream = false;
		}
	}
	else
	{
		result = eSE_MiscFatalError;
	}

	return result;
}


// Description:
//	 Close a connection
// Arguments:
//	 sockIdx - index of socket
// Return:
//	 Result code.
ESocketError
CCryTCPServiceFactory::CloseConnection(
	int32&					sockIdx )
{
	assert( ( sockIdx < (int32)m_maxSockets ) && ( sockIdx > -1 ) );

	if ( m_pSockets[ sockIdx ] != INVALID_SOCKET )
	{
		::closesocket( m_pSockets[ sockIdx ] );
		 m_pSockets[ sockIdx ] = INVALID_SOCKET;
		 sockIdx = INVALID_SOCKET_INDEX;
		--m_numSockets;
	}

	return eSE_Ok;
}

ICryTCPServicePtr CCryTCPServiceFactory::CreateTCPService(const char* pUrl, uint16 port)
{
	ScopedSwitchToGlobalHeap useGlobalHeap;

	ICryTCPServicePtr		pService;

	pService = new CCryTCPService(this);

	if ( pService )
	{
		pService->Initialise( pUrl, port );
	}

	return pService;
}
